<?php
/**
 * Hooks class for controllers
 * 
 * Providing event-like functionality with FILO stack
 * 
 * @version:	0.1
 * @package:	core
 * @author:		alex_ez
 * @created:	28 april 2010
 * 
 * @copyright:	(c) 2010 alex <qfox@ya.ru> yaroshevich aka alex_ez. All rights reserved.
 */

class Hooks
{
	/**
	 * Owner
	 * @var controller
	 */
	protected $controller = null;
	
	/**
	 * Stack
	 * @var array
	 */
	protected $stack = array( );
	
	/**
	 * Constructor gets controller instance
	 * @param controller $controller
	 */
	public function __construct( controller &$controller )
	{
		$this ->controller =& $controller;
		$this ->bind( 'load', array( &$this ->controller, 'onLoad' ) );
		$this ->bind( 'initialize', array( &$this ->controller, 'onInitialize' ) );
		$this ->bind( 'display', array( &$this ->controller, 'onDisplay' ) );
	}
	
	/**
	 * Binds callback to event
	 *
	 * @param string $event
	 * @param callback $callback
	 */
	public function bind( $event, $callback )
	{
		if( !is_callable( $callback, true, $callable_name ) )
			return false;
		
		$this ->push( $event, $callback );
		return $this;
	}
	
	/**
	 * Unbinds some count of callbacks for event from last
	 * 
	 * @param string $event
	 * @param int $quantity
	 */
	public function unbind( $event, $quantity = 1 )
	{
		while( $quantity-- > 0 )
			$this ->pop( $event );
		
		return $this;
	}
	
	/**
	 * Fires event and calls all binded callbacks from first to last
	 * 
	 * @param string $event
	 * @param mixed $params
	 */
	public function fire( $event, $params = null )
	{
		$walker = new HooksWalker( $event, $params );
		foreach( $this ->getStack( $event ) as $callback )
		if( is_callable( $callback, false, $callable_name ) )
		{
			if( !$walker ->alive( ) )
				die( 'hm?' );
			$exists = is_array( $callback )
				? method_exists( $callback[0], $callback[1] )
				: function_exists( $callback );
			if( !$exists ) continue;
			
			call_user_func_array( $callback, array( &$walker ) );
			fb::info( $callable_name . (!$walker ->alive( ) ? ' - last' : ''), 'hooked '.$event );
			if( !$walker ->alive( ) )
				break;
		}
		
		return $this;
	}
	
	/**
	 * Stack initialization
	 *
	 * @param string $event
	 */
	protected function initStack( $event )
	{
		if( isset( $this ->stack[ $event ] )
			&& is_array( $this ->stack[ $event ] ) )
			return;
		
		$this ->stack[ $event ] = array( );
	}
	
	/**
	 * Gets full binds stack
	 * 
	 * @param string $event
	 * @return array callbacks for event
	 */
	protected function getStack( $event )
	{
		$this ->initStack( $event );
		return $this ->stack[ $event ];
	}
	
	/**
	 * Push callback to event stack
	 * 
	 * @param string $event
	 * @param callback $callback
	 */
	protected function push( $event, $callback )
	{
		$this ->initStack( $event );
		array_push( $this ->stack[ $event ], $callback );
	}
	
	/**
	 * Pop callback from stack event
	 *
	 * @param string $event
	 * @return callback
	 */
	protected function pop( $event )
	{
		$this ->initStack( $event );
		
		if( empty( $this ->stack[ $event ] ) )
			return null;
		
		return array_pop( $this ->stack[ $event ] );
	}

}


/**
 * Object that passes through all callbacks from first to last
 */
class HooksWalker
{
	/**
	 * Event type
	 * @var string
	 */
	protected $event = null;

	/**
	 *
	 * @var array
	 */
	protected $params = null;

	/**
	 * 
	 * @var boolean 
	 */
	protected $walking = true;

	/**
	 * 
	 */
	public function __construct( $event, $params = null )
	{
		$this ->event = $event;
		$this ->params = $params;
	}

	public function stop( )
	{
		$this ->walking = false;
		return $this;
	}

	public function alive( )
	{
		return $this ->walking;
	}

}